package cn.mydsl.restful

import io.vertx.core.AbstractVerticle
import io.vertx.core.Vertx
import io.vertx.core.http.HttpHeaders
import io.vertx.core.http.HttpMethod
import io.vertx.core.http.HttpServerRequest
import io.vertx.core.http.HttpServerResponse
import io.vertx.core.json.Json
import io.vertx.ext.web.Router
import io.vertx.ext.web.handler.StaticHandler
import io.vertx.core.json.JsonObject
import io.vertx.ext.jdbc.JDBCClient
import io.vertx.core.json.EncodeException
import io.vertx.ext.web.handler.BodyHandler
import io.vertx.ext.web.handler.CookieHandler
import kotlin.concurrent.timer
import kotlin.properties.Delegates
import io.vertx.ext.auth.jwt.JWTAuth
import io.vertx.ext.web.RoutingContext
import io.vertx.ext.web.handler.JWTAuthHandler


enum class ErroType(val title: String) {
    InvalidParam("无效的参数"), SqlExecExcp("SQL执行异常"), SqlConExcp("SQL连接异常"),InitExcp("初始化异常"),Unknown("未知错误")
    ,Unauthorized("需登录访问"),Forbidden("禁止访问"),NotFound("未找到资源")
}

data class ErroMsg(var title :String?=null,val type:ErroType=ErroType.Unknown,val message :String?=null,val cause :String?=null)

fun HttpServerResponse.html(s:String): Unit {
    return this.putHeader("Content-Type", "text/html;charset=utf-8").end(s)
}

fun HttpServerResponse.erro(erroMsg:ErroMsg): Unit {
    this.putHeader("Content-Type", "application/json;charset=utf-8")
     when(erroMsg.type){
         ErroType.InvalidParam->this.setStatusCode(400)
         ErroType.SqlExecExcp ->this.setStatusCode(500)
         ErroType.SqlConExcp ->this.setStatusCode(500)
         ErroType.Unknown->this.setStatusCode(500)
         ErroType.InitExcp->this.setStatusCode(500)
         ErroType.Unauthorized->this.setStatusCode(401)
         ErroType.Forbidden->this.setStatusCode(403)
         ErroType.NotFound->this.setStatusCode(404)
         else->this.setStatusCode(500)
     }
    if(erroMsg.title==null){
        erroMsg.title==erroMsg.type.title
    }
    return this.end(erroMsg.toJsonString())
}


fun HttpServerRequest.page(): Int? {
    try {
        return this.getParam("page")?.toInt()
    } catch (ex: Exception) {
        return null
    }
}

fun HttpServerRequest.size(): Int? {
    try {
        var size = this.getParam("size")?.toInt()
        if (size != null && size >= 1 && size <= 100) {
            return size
        } else {
            return 10//默认10条
        }
    } catch (ex: Exception) {
        return null
    }
}

/**
 * 查询时带上,关联表的字段
 * 关联表可分为 主表(belong)和子表(many)
 * 优先以关联表是主表： 本表belongs_to关联表（特点是本表有关联表_id字段）
 * 不行以关联表是子表： 本表 has_many 关联表（特点是关联表有本表_id字段）
 */
fun HttpServerRequest.joint(): List<String>? {
    var js=this.getParam("joint")
    if(js!=null ){
       return js.split("|")
    }
    return null
}

fun Any.toJsonString(): String {
    try {
        if (this is Exception) {
            //异常，需格式化固定字段输出
            return this.toErroMsg().toJsonString()
        } else if (CommonResourceServer.devOrTestEnv) {
            //开发和测试环境，输出空格对齐的友好json
            return Json.encodePrettily(this)
        } else {
            //开发和测试环境，输出紧凑的json
            return Json.encode(this)
        }

    } catch (encodeException: EncodeException) {
        println(encodeException.toErroMsg("编码json字符串失败").toJsonString())
    }
    return "{}"
}
fun Any.printJsonString(): Unit {
    println(this.toJsonString())
}
fun Exception.toErroMsg(title: String?=null, type:ErroType=ErroType.Unknown): ErroMsg {
    return ErroMsg(title,type,this.message,this.cause.toString())
}

fun Throwable.toErroMsg(title: String?=null, type:ErroType=ErroType.Unknown): ErroMsg {
    return ErroMsg(title,type,this.message,this.cause.toString())
}

fun RoutingContext.loadUser(jwtAuth:JWTAuth) : RoutingContext {
    var token=""
    var cookieAuthorization= this.getCookie(HttpHeaders.AUTHORIZATION.toString())
    val headerAuthorization = this.request().headers().get(HttpHeaders.AUTHORIZATION)
    if (headerAuthorization != null) {
        //优先相信请求头的认证信息
        val var10 = headerAuthorization.split(" ".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray()
        if (var10.size == 2 && var10[0].startsWith("Bearer",true)) {
            token = var10[1]
        }
    }else if(cookieAuthorization!=null && cookieAuthorization.value!=null){
        //没有头认证信息，则相信cookie
        val var10=cookieAuthorization.value.split("%20".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray()
        if (var10.size == 2 && var10[0].startsWith("Bearer",true)) {
            token = var10[1]
        }
    }
    //从token提取认证信息
    if(token.trim().isNotEmpty()){
        jwtAuth.authenticate(JsonObject().put("jwt", token).put("options", JsonObject()), { res ->
            if (res.succeeded()) {
                this.setUser(res.result())
            }
        })
    }
    return this
}

class CommonResourceServer : AbstractVerticle() {

    companion object {
        class User(val map: Map<String, Any?>) {
            val uid: String by map
            val oid: String by map
            val jid: String by map
        }
        val user = User(mapOf(
                "uid" to "1",
                "oid" to "2",
                "jid" to "s111"
        ))

        //读取环境变量值
        fun getEnvironmentVariables(name: String, defaultValue: String): String {
            var value = System.getenv(name);
            if (value != null && value.trim().isNotEmpty()) {
                return value.trim()
            } else {
                return defaultValue
            }
        }

        //开发或测试环境
        val devOrTestEnv = if (System.getenv("DEV_OR_TEST_ENV") != null && System.getenv("DEV_OR_TEST_ENV").trim().equals("false", true)) false else true
        //默认数据库
        var defaultDbName: String by Delegates.notNull()
        //默认数据连接池
        var jdbcClient: JDBCClient by Delegates.notNull()
        var tableResource: TableResource by Delegates.notNull()
        var userResource: UserResource by Delegates.notNull()
        var authProvider:JWTAuth by Delegates.notNull()
        //main函数
        @JvmStatic fun main(args: Array<String>) {
            var vertx = Vertx.vertx()
            defaultDbName = getEnvironmentVariables("DB_DATABASE", "information_schema")
            jdbcClient = JDBCClient.createShared(vertx, JsonObject()
                    .put("url", "jdbc:mysql:// ${getEnvironmentVariables("DB_URI", "localhost:3306")}/$defaultDbName?useUnicode=true&characterEncoding=UTF-8&createDatabaseIfNotExist=true&autoReconnect=true&useSSL=false")
                    .put("driver_class", "com.mysql.cj.jdbc.Driver")
                    .put("user", getEnvironmentVariables("DB_USENAME", "root"))
                    .put("password", getEnvironmentVariables("DB_PASSWORD", "23050210@qq.com")))

            var publicKey="MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgNyqMbehSVf5AxAVO+v/K3FmgkvwKeI0VcySCDjl/Lag55EuOxBWUPLKBu/ujnpK34mohr0uhPn/UhawNuXM96zz1wKEFUqE8F9Srwg/V2o+Ugl8ZuCQxtSpCVVwc+RfpL60Y5zWYlrYO2JTmCIhfZ9cG4NzE0n/TV6PHeVsjpucFiMcUD+V6nHDSzuXCOVnp1UIuaf8cL3y1EXDanndYeABeOt2xg3elXLNO5VGJTKfhstbfn/YspdBScA7tGR5uQ4upHD4pIg6OxCyTs27DvnIAQMdQ+OnMJR02e4gC1eDwTxsw/y2UcsZFthfK77lvACPySBukiK+C0qjLBfj9QIDAQAB"
            val config = JsonObject()
                    .put("public-key", publicKey)
            authProvider = JWTAuth.create(vertx, config)
            tableResource= TableResource(jdbcClient,defaultDbName)
            //初次启动时,数据库可能未启动好,需要等待,每8秒获参数一次,直至成功获取到元数据
            timer(initialDelay = 0, period = 8000) {
                tableResource.initializeMetadata({ cancel() })
            }
            //启动Web服务,并阻塞mian,直至成功获取到元数据
            while (true) {
                if (tableResource.Ready()) {
                    userResource= UserResource(jdbcClient,vertx)
                    vertx.deployVerticle(CommonResourceServer())
                    return
                } else {
                    println("数据库,暂时,没有表用于服务")
                    Thread.sleep(5000)
                }
            }
        }
    }

    override fun start() {
        var router = Router.router(vertx)
        //cookie和读取body的全局handler
        router.route().handler(CookieHandler.create())
        router.route().method(HttpMethod.PUT).method(HttpMethod.POST).handler(BodyHandler.create().setBodyLimit(8 * 1024))
        //读取数据库元数据
        router.get("/initializeMetadata/").handler{ routingContext -> tableResource.getInitializeMetadata(routingContext) }
        //自主决定，是否需要认证和权限的URL
        router.post("/login/").handler {routingContext ->userResource.Login(routingContext)}
        router.post("/logout/").handler {routingContext ->userResource.Logout(routingContext)}
        router.get("/:table/").handler {routingContext ->tableResource.getTable(routingContext.loadUser(authProvider)) }
        //需要集中认证，并传递认证信息的URL
        router.route("/admin/*").handler(JWTAuthHandler.create(authProvider).setIgnoreExpiration(false));
        router.get("/token/").handler { ctx->ctx.response().end("token")}
        router.route().handler(StaticHandler.create("static").setCachingEnabled(false))
        //兜底全部匹配不上时，重定向到failureHandler,统一处理
        router.route().handler { ctx -> ctx.fail(404) }
        router.route().failureHandler {
            ctx -> if (ctx.statusCode() == 404)
            ctx.response().erro(ctx.failure()?.toErroMsg("未找到资源：${ctx.request().path()}",ErroType.NotFound)?:ErroMsg("未找到资源：${ctx.request().path()}",ErroType.NotFound))
        else if (ctx.statusCode() == 401)
            ctx.response().erro(ctx.failure()?.toErroMsg("需登录才能访问：${ctx.request().path()}",ErroType.Unauthorized)?:ErroMsg("需登录才能访问：${ctx.request().path()}",ErroType.Unauthorized))
        else if (ctx.statusCode() == 403)
            ctx.response().erro(ctx.failure()?.toErroMsg("禁止访问：${ctx.request().path()}",ErroType.Forbidden)?:ErroMsg("禁止访问：${ctx.request().path()}",ErroType.Forbidden))
        else if (ctx.statusCode() == -1)
            ctx.response().erro( ctx.failure()?.toErroMsg("访问错误：${ctx.request().path()}")?:ErroMsg("访问错误：${ctx.request().path()}"))
        else ctx.reroute("/static/other.html") }
        router.exceptionHandler({ e -> println("web-exceptionHandler:${e.printStackTrace()}") })
        println("server running on 8080")
        vertx.createHttpServer().requestHandler({ handler -> router.accept(handler) }).listen(8080)
    }
}
